// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/command_line.h"
#include "base/macros.h"
#include "base/strings/stringprintf.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/shell/browser/shell.h"
#include "net/dns/mock_host_resolver.h"
#include "url/gurl.h"

namespace content {

namespace {

    // A dummy WebContentsDelegate which tracks whether CloseContents() has been
    // called. It refuses the actual close but keeps track of whether the renderer
    // requested it.
    class CloseTrackingDelegate : public WebContentsDelegate {
    public:
        CloseTrackingDelegate()
            : close_contents_called_(false)
        {
        }

        bool close_contents_called() const { return close_contents_called_; }

        void CloseContents(WebContents* source) override
        {
            close_contents_called_ = true;
        }

    private:
        bool close_contents_called_;

        DISALLOW_COPY_AND_ASSIGN(CloseTrackingDelegate);
    };

} // namespace

class OpenedByDOMTest : public ContentBrowserTest {
protected:
    void SetUpCommandLine(base::CommandLine* command_line) override
    {
        // Use --site-per-process to force process swaps on cross-site navigations.
        IsolateAllSitesForTesting(command_line);
    }

    bool AttemptCloseFromJavaScript(WebContents* web_contents)
    {
        CloseTrackingDelegate close_tracking_delegate;
        WebContentsDelegate* old_delegate = web_contents->GetDelegate();
        web_contents->SetDelegate(&close_tracking_delegate);

        const char kCloseWindowScript[] =
            // Close the window.
            "window.close();"
            // Report back after an event loop iteration; the close IPC isn't sent
            // immediately.
            "setTimeout(function() {"
            "window.domAutomationController.send(0);"
            "});";
        int dummy;
        CHECK(ExecuteScriptAndExtractInt(web_contents, kCloseWindowScript, &dummy));

        web_contents->SetDelegate(old_delegate);
        return close_tracking_delegate.close_contents_called();
    }

    Shell* OpenWindowFromJavaScript(Shell* shell, const GURL& url)
    {
        // Wait for the popup to be created and for it to have navigated.
        ShellAddedObserver new_shell_observer;
        TestNavigationObserver nav_observer(NULL);
        nav_observer.StartWatchingNewWebContents();
        CHECK(ExecuteScript(
            shell, base::StringPrintf("window.open('%s')", url.spec().c_str())));
        nav_observer.Wait();
        return new_shell_observer.GetShell();
    }
};

// Tests that window.close() does not work on a normal window that has navigated
// a few times.
IN_PROC_BROWSER_TEST_F(OpenedByDOMTest, NormalWindow)
{
    ASSERT_TRUE(embedded_test_server()->Start());

    // window.close is allowed if the window was opened by DOM OR the back/forward
    // list has only one element. Navigate a bit so the second condition is false.
    GURL url1 = embedded_test_server()->GetURL("/site_isolation/blank.html?1");
    GURL url2 = embedded_test_server()->GetURL("/site_isolation/blank.html?2");
    NavigateToURL(shell(), url1);
    NavigateToURL(shell(), url2);

    // This window was not opened by DOM, so close does not reach the browser
    // process.
    EXPECT_FALSE(AttemptCloseFromJavaScript(shell()->web_contents()));
}

// Tests that window.close() works in a popup window that has navigated a few
// times.
IN_PROC_BROWSER_TEST_F(OpenedByDOMTest, Popup)
{
    ASSERT_TRUE(embedded_test_server()->Start());

    GURL url1 = embedded_test_server()->GetURL("/site_isolation/blank.html?1");
    GURL url2 = embedded_test_server()->GetURL("/site_isolation/blank.html?2");
    GURL url3 = embedded_test_server()->GetURL("/site_isolation/blank.html?3");
    NavigateToURL(shell(), url1);

    Shell* popup = OpenWindowFromJavaScript(shell(), url2);
    NavigateToURL(popup, url3);
    EXPECT_TRUE(AttemptCloseFromJavaScript(popup->web_contents()));
}

// Tests that window.close() works in a popup window that has navigated a few
// times and swapped processes.
IN_PROC_BROWSER_TEST_F(OpenedByDOMTest, CrossProcessPopup)
{
    host_resolver()->AddRule("*", "127.0.0.1");
    ASSERT_TRUE(embedded_test_server()->Start());

    GURL url1 = embedded_test_server()->GetURL("/site_isolation/blank.html?1");

    GURL url2 = embedded_test_server()->GetURL("/site_isolation/blank.html?2");
    GURL::Replacements replace_host;
    replace_host.SetHostStr("foo.com");
    url2 = url2.ReplaceComponents(replace_host);

    GURL url3 = embedded_test_server()->GetURL("/site_isolation/blank.html?3");
    url3 = url3.ReplaceComponents(replace_host);

    NavigateToURL(shell(), url1);

    Shell* popup = OpenWindowFromJavaScript(shell(), url2);
    NavigateToURL(popup, url3);
    EXPECT_TRUE(AttemptCloseFromJavaScript(popup->web_contents()));
}

} // namespace content
